#include "get-page.hh"
#include <iostream>
GetPage::GetPage(string host, string file, string port, string output_file)
  : host_(host), file_(file), port_(port), output_file_(output_file){}

size_t GetPage::SIZE_PER_READ = 1<<16;

void GetPage::get_addr_list(){
  //init return variable and hint
  struct addrinfo hints = {0}, *res;
  hints.ai_socktype = SOCK_STREAM; // TCP connection
  hints.ai_family = AF_UNSPEC;	   // IPv4 or IPv6

  // try getting info
  cout << "Getting address information" << endl;
  int err(getaddrinfo(host_.c_str(), port_.c_str(), &hints, &res));
  if (err){
    cerr << "getaddrinfo: " << gai_strerror(err) << endl;
    return;
  }

  // return when success
  for(struct addrinfo* p(res); p; p = p->ai_next){
    addr_list_.push_back(p);
  }
  cout << "Number of results: " << addr_list_.size() << endl << endl;
}

bool GetPage::get_connect(struct addrinfo& addr){
  //get sock identity
  cout << "Get sockfd" << endl;
  int sockfd(socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol));
  if (sockfd == -1){
    perror("socket");
    return false;
  }
  cout << "Ok. Sockfd: " << sockfd << endl << endl;

  // connect to server
  cout << "Connect to server" << endl;
  int err(connect(sockfd, addr.ai_addr, addr.ai_addrlen));
  if (err == -1){
    perror("connect");
    return false;
  }
  cout << "Ok" << endl << endl;

  return send_request(sockfd);
}

bool GetPage::send_request(int sockfd){
  string request = "GET " + file_ + " HTTP/1.1\r\nHost: " + host_ + "\r\n\r\n";
  cout << "Request string: " << endl;
  cout << "\"" << request << "\"" << endl;
  cout << "Sending..." << endl;
  int cnt(send(sockfd, request.c_str(), request.length(), 0));
  if (cnt < static_cast<int>(request.length())){
    perror("Send error");
    return false;
  }
  cout << "Send ok" << endl << endl;

  return retrieve_file(sockfd);
}

void GetPage::finish(fstream& fs, int sockfd, char *mem){
  if (fs.is_open())
    fs.close();
  close(sockfd);
  delete[] mem;
}

bool GetPage::retrieve_file(int sockfd){
  cout << "Retrieving file .." << endl;
  char *response = new char[SIZE_PER_READ];
  fstream fs(output_file_, ios::binary | ios::out);
  if (!fs.good()){
    cerr << "Open file to write failed" << endl;
    finish(fs, sockfd, response);
    return false;
  }

  int cnt = 0;
  bool first_time = true;
  do{
    cnt = recv(sockfd, response, SIZE_PER_READ, 0);
    if (cnt < 0){
      perror("recv");
      finish(fs, sockfd, response);
      return false;
    }
    if (first_time){
      int id = start_of_data(response, cnt);
      if (id != -1)
	fs.write(&response[id], cnt - id);
      else
	fs.write(response, cnt);
      first_time = false;
    }
    else
      fs.write(response, cnt);
  } while (cnt);

  finish(fs, sockfd, response);
  cout << "OK" <<endl << endl;
  return true;
}

int GetPage::start_of_data(char*& all_data, int& cnt){
  const char pattern[4] = {'\r', '\n', '\r', '\n'};
  for(int i = 0; i + 3 < cnt; i++)
    {
      bool ok = true;
      for(int j = 0; j < 4; j++)
	if (all_data[i + j] != pattern[j])
	  {
	    ok = false;
	    break;
	  }
      if (ok)
	return i + 4;
    }
  return -1;
}

string GetPage::get_page(){
  get_addr_list();
  for(vector<struct addrinfo*>::iterator it(addr_list_.begin());
      it != addr_list_.end();
      it++)
    if (get_connect(**it))
      {
	free_addr_list();
	return "SUCCESS";
      }

  free_addr_list();
  return "FAILED";
}


void GetPage::free_addr_list(){
  for(vector<struct addrinfo*>::iterator it(addr_list_.begin());
      it != addr_list_.end();
      it++)
    free(*it);
}
